/*
 * Media controller test application
 *
 * Copyright (C) 2010-2014 Ideas on board SPRL
 *
 * Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include <ctype.h>
#include <dirent.h>
#include <fcntl.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#ifndef __ANDROID__
    #include <v4l2subdev.h>
#endif
#include <linux/media.h>
#include <linux/videodev2.h>

#include "options.h"
#include "v4l2subdev.h"

#define MEDIA_DEVNAME_DEFAULT "/dev/media0"

struct media_options media_opts = {
    .devname = MEDIA_DEVNAME_DEFAULT,
};

static void usage(const char* argv0)
{
    unsigned int i;

    printf("%s [options]\n", argv0);
    printf("-d, --device dev	Media device name (default: %s)\n", MEDIA_DEVNAME_DEFAULT);
    printf("-e, --entity name	Print the device name associated with the "
           "given "
           "entity\n");
    printf("-V, --set-v4l2 v4l2	Comma-separated list of formats to setup\n");
    printf("    --get-v4l2 pad	Print the active format on a given pad\n");
    printf("    --get-dv pad        Print detected and current DV timings on a "
           "given pad\n");
    printf("    --set-dv pad	Configure DV timings on a given pad\n");
    printf("-h, --help		Show verbose help and exit\n");
    printf("-i, --interactive	Modify links interactively\n");
    printf("-l, --links links	Comma-separated list of link descriptors to "
           "setup\n");
    printf("    --known-mbus-fmts	List known media bus formats and their "
           "numeric values\n");
    printf("-p, --print-topology	Print the device topology. If an entity\n");
    printf("			is specified through the -e option, print\n");
    printf("			information for that entity only.\n");
    printf("    --print-dot		Print the device topology as a dot "
           "graph\n");
    printf("-r, --reset		Reset all links to inactive\n");
    printf("-v, --verbose		Be verbose\n");
    printf("\n");
    printf("Links and formats are defined as\n");
    printf("\tlinks           = link { ',' link } ;\n");
    printf("\tlink            = pad '->' pad '[' flags ']' ;\n");
    printf("\tpad             = entity ':' pad-number ;\n");
    printf("\tentity          = entity-number | ( '\"' entity-name '\"' ) ;\n");
    printf("\n");
    printf("\tv4l2            = pad '[' v4l2-properties ']' ;\n");
    printf("\tv4l2-properties = v4l2-property { ',' v4l2-property } ;\n");
    printf("\tv4l2-property   = v4l2-mbusfmt | v4l2-crop | v4l2-interval\n");
    printf("\t                | v4l2-compose | v4l2-interval ;\n");
    printf("\tv4l2-mbusfmt    = 'fmt:' fcc '/' size ; { 'field:' v4l2-field ; } "
           "{ 'colorspace:' v4l2-colorspace ; }\n");
    printf("\t                   { 'xfer:' v4l2-xfer-func ; } { 'ycbcr-enc:' "
           "v4l2-ycbcr-enc-func ; }\n");
    printf("\t                   { 'quantization:' v4l2-quant ; }\n");
    printf("\tv4l2-crop       = 'crop:' rectangle ;\n");
    printf("\tv4l2-compose    = 'compose:' rectangle ;\n");
    printf("\tv4l2-interval   = '@' numerator '/' denominator ;\n");
    printf("\n");
    printf("\trectangle       = '(' left ',' top, ')' '/' size ;\n");
    printf("\tsize            = width 'x' height ;\n");
    printf("\n");
    printf("where the fields are\n");
    printf("\tentity-number   Entity numeric identifier\n");
    printf("\tentity-name     Entity name (string) \n");
    printf("\tpad-number      Pad numeric identifier\n");
    printf("\tflags           Link flags (0: inactive, 1: active)\n");
    printf("\tfcc             Format FourCC\n");
    printf("\twidth           Image width in pixels\n");
    printf("\theight          Image height in pixels\n");
    printf("\tnumerator       Frame interval numerator\n");
    printf("\tdenominator     Frame interval denominator\n");
    printf("\tv4l2-field      One of the following:\n");

    for (i = V4L2_FIELD_ANY; i <= V4L2_FIELD_INTERLACED_BT; i++) {
        printf("\t                %s\n", v4l2_subdev_field_to_string(i));
    }

    printf("\tv4l2-colorspace One of the following:\n");

    for (i = V4L2_COLORSPACE_DEFAULT; i <= V4L2_COLORSPACE_DCI_P3; i++) {
        printf("\t                %s\n", v4l2_subdev_colorspace_to_string(i));
    }

    printf("\tv4l2-xfer-func  One of the following:\n");

    for (i = V4L2_XFER_FUNC_DEFAULT; i <= V4L2_XFER_FUNC_SMPTE2084; i++) {
        printf("\t                %s\n", v4l2_subdev_xfer_func_to_string(i));
    }

    printf("\tv4l2-quant      One of the following:\n");

    for (i = V4L2_QUANTIZATION_DEFAULT; i <= V4L2_QUANTIZATION_LIM_RANGE; i++) {
        printf("\t                %s\n", v4l2_subdev_quantization_to_string(i));
    }
}

#define OPT_PRINT_DOT 256
#define OPT_GET_FORMAT 257
#define OPT_SET_DV 258
#define OPT_LIST_KNOWN_MBUS_FMTS 259
#define OPT_GET_DV 260

static struct option opts[] = {
    {"device", 1, 0, 'd'},
    {"entity", 1, 0, 'e'},
    {"set-format", 1, 0, 'f'},
    {"set-v4l2", 1, 0, 'V'},
    {"get-format", 1, 0, OPT_GET_FORMAT},
    {"get-v4l2", 1, 0, OPT_GET_FORMAT},
    {"get-dv", 1, 0, OPT_GET_DV},
    {"set-dv", 1, 0, OPT_SET_DV},
    {"help", 0, 0, 'h'},
    {"interactive", 0, 0, 'i'},
    {"links", 1, 0, 'l'},
    {"known-mbus-fmts", 0, 0, OPT_LIST_KNOWN_MBUS_FMTS},
    {"print-dot", 0, 0, OPT_PRINT_DOT},
    {"print-topology", 0, 0, 'p'},
    {"reset", 0, 0, 'r'},
    {"verbose", 0, 0, 'v'},
    {},
};

static void list_known_mbus_formats(void)
{
    unsigned int ncodes;
    const unsigned int* code = v4l2_subdev_pixelcode_list(&ncodes);

    while (ncodes--) {
        const char* str = v4l2_subdev_pixelcode_to_string(*code);
        int spaces = 30 - (int)strlen(str);

        if (*code == 0) {
            break;
        }

        if (spaces < 0) {
            spaces = 0;
        }

        printf("%s %*c 0x%4.4x\n", str, spaces, ' ', *code);

        code++;
    }
}

static const char* make_devname(const char* device)
{
    static char newdev[300];
    struct dirent* ep;
    DIR* dp;

    if (!access(device, F_OK)) {
        return device;
    }

    if (device[0] >= '0' && device[0] <= '9' && strlen(device) <= 3) {
        snprintf(newdev, sizeof(newdev), "/dev/media%s", device);
        return newdev;
    }

    dp = opendir("/dev");
    if (dp == NULL) {
        return device;
    }

    while ((ep = readdir(dp))) {
        const char* name = ep->d_name;

        if (!memcmp(name, "media", 5) && isdigit(name[5])) {
            struct media_device_info mdi;
            int ret;
            int fd;

            snprintf(newdev, sizeof(newdev), "/dev/%s", name);
            fd = open(newdev, O_RDWR);
            if (fd < 0) {
                continue;
            }
            ret = ioctl(fd, MEDIA_IOC_DEVICE_INFO, &mdi);
            close(fd);
            if (!ret && !strcmp(device, mdi.bus_info)) {
                closedir(dp);
                return newdev;
            }
        }
    }
    closedir(dp);
    return device;
}

int parse_cmdline(int argc, char** argv)
{
    int opt;

    if (argc == 1) {
        usage(argv[0]);
        return 1;
    }

    /* parse options */
    while ((opt = getopt_long(argc, argv, "d:e:f:hil:prvV:", opts, NULL)) != -1) {
        switch (opt) {
            case 'd':
                media_opts.devname = make_devname(optarg);
                break;

            case 'e':
                media_opts.entity = optarg;
                break;

                /* 'f' is supported for backward compatibility reasons and will
                 * be removed later.
                 */
            case 'f':
                fprintf(stderr, "Warning: the -f option is deprecated "
                                "and has been replaced by -V.\n");
            /* fall through */
            case 'V':
                media_opts.formats = optarg;
                break;

            case 'h':
                usage(argv[0]);
                exit(0);

            case 'i':
                media_opts.interactive = 1;
                break;

            case 'l':
                media_opts.links = optarg;
                break;

            case 'p':
                media_opts.print = 1;
                break;

            case 'r':
                media_opts.reset = 1;
                break;

            case 'v':
                media_opts.verbose = 1;
                break;

            case OPT_PRINT_DOT:
                media_opts.print_dot = 1;
                break;

            case OPT_GET_FORMAT:
                media_opts.fmt_pad = optarg;
                break;

            case OPT_GET_DV:
                media_opts.get_dv_pad = optarg;
                break;

            case OPT_SET_DV:
                media_opts.dv_pad = optarg;
                break;

            case OPT_LIST_KNOWN_MBUS_FMTS:
                list_known_mbus_formats();
                exit(0);

            default:
                printf("Invalid option -%c\n", opt);
                printf("Run %s -h for help.\n", argv[0]);
                return 1;
        }
    }

    return 0;
}
